﻿using System;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
using Whitepad.Properties;

namespace Whitepad
{
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : Window
	{
		Point currentDrag;
		Button currentState;

		public MainWindow()
		{
			InitializeComponent();
			inkCanvas.EraserShape = new EllipseStylusShape(25, 25);
			inkCanvas.DefaultDrawingAttributes.StylusTip = StylusTip.Ellipse;
			inkCanvas.Strokes.StrokesChanged += Strokes_StrokesChanged;
			currentState = btnBlack;
			Highlight(btnBlack);
		}

		private void btnNew_Click(object sender, RoutedEventArgs e)
		{
			FileNew();
		}

		private void btnOpen_Click(object sender, RoutedEventArgs e)
		{
			FileOpen();
		}

		private void btnSave_Click(object sender, RoutedEventArgs e)
		{
			FileSave();
		}

		private void btnSaveAs_Click(object sender, RoutedEventArgs e)
		{
			FileSaveAs();
		}

		private void btnScreenshot_Click(object sender, RoutedEventArgs e)
		{
			DrawingVisual drawing = new DrawingVisual();
			using (DrawingContext context = drawing.RenderOpen())
			{
				context.DrawRectangle(new VisualBrush(inkCanvas), null, new Rect(new Size((int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight)));
			}
			Matrix matrix = PresentationSource.FromVisual(inkCanvas).CompositionTarget.TransformToDevice;
			double dpiX = matrix.M11;
			double dpiY = matrix.M22;
			RenderTargetBitmap bmp = new RenderTargetBitmap((int)(inkCanvas.ActualWidth * dpiX), (int)(inkCanvas.ActualHeight * dpiY), 96 * dpiX, 96 * dpiY, PixelFormats.Pbgra32);
			bmp.Render(drawing);
			PngBitmapEncoder encoder = new PngBitmapEncoder();
			encoder.Frames.Add(BitmapFrame.Create(bmp));
			SaveFileDialog dlg = new SaveFileDialog();
			dlg.Filter = "PNG images (*.png)|*.png";
			dlg.DefaultExt = "png";
			if (dlg.ShowDialog() != true)
			{
				return;
			}
			using (FileStream stream = new FileStream(dlg.FileName, FileMode.Create))
			{
				encoder.Save(stream);
			}
		}

		private void btnClose_Click(object sender, RoutedEventArgs e)
		{
			this.Close();
		}

		private void btnBlack_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.DefaultDrawingAttributes.Color = Colors.Black;
			inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnBlack);
		}

		private void btnRed_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.DefaultDrawingAttributes.Color = Colors.Red;
			inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnRed);
		}

		private void btnGreen_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.DefaultDrawingAttributes.Color = Colors.Green;
			inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnGreen);
		}

		private void btnBlue_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.DefaultDrawingAttributes.Color = Colors.Blue;
			inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnBlue);
		}

		private void btnEraser_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnEraser);
		}

		private void btnMove_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.EditingMode = InkCanvasEditingMode.None;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnMove);
		}

		private void btnZoomrot_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.EditingMode = InkCanvasEditingMode.None;
			inkCanvas.IsManipulationEnabled = false;
			Highlight(btnZoomrot);
		}

		private void btnMulti_Click(object sender, RoutedEventArgs e)
		{
			inkCanvas.EditingMode = InkCanvasEditingMode.None;
			inkCanvas.IsManipulationEnabled = true;
			Highlight(btnMulti);
		}

		private void Highlight(Button button)
		{
			currentState.Background = null;
			currentState = button;
			currentState.Background = (Brush)this.Resources["highlight"];
		}

		private void inkCanvas_MouseDown(object sender, MouseButtonEventArgs e)
		{
			if (currentState != btnMove && currentState != btnZoomrot)
			{
				return;
			}
			currentDrag = e.GetPosition(inkCanvas);
			inkCanvas.CaptureMouse();
		}

		private void inkCanvas_MouseMove(object sender, MouseEventArgs e)
		{
			if (currentState != btnMove && currentState != btnZoomrot || !inkCanvas.IsMouseCaptured)
			{
				return;
			}
			Point drag = e.GetPosition(inkCanvas);
			Vector transform = drag - currentDrag;
			Matrix matrix = Matrix.Identity;
			double scale = 1;
			if (currentState == btnMove)
			{
				matrix.Translate(transform.X, transform.Y);
			}
			else if (currentState == btnZoomrot)
			{
				scale = Math.Pow(1.01, -transform.Y);
				matrix.ScaleAt(scale, scale, inkCanvas.ActualWidth / 2, inkCanvas.ActualHeight / 2);
				matrix.RotateAt(transform.X / 3, inkCanvas.ActualWidth / 2, inkCanvas.ActualHeight / 2);
			}
			foreach (Stroke stroke in inkCanvas.Strokes)
			{
				stroke.Transform(matrix, false);
				stroke.DrawingAttributes.Width *= scale;
				stroke.DrawingAttributes.Height *= scale;
			}
			currentDrag = drag;
		}

		private void inkCanvas_MouseUp(object sender, MouseButtonEventArgs e)
		{
			if (currentState != btnMove && currentState != btnZoomrot || !inkCanvas.IsMouseCaptured)
			{
				return;
			}
			inkCanvas.ReleaseMouseCapture();
		}

		private void inkCanvas_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
		{
			Matrix matrix = Matrix.Identity;
			matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);
			matrix.RotateAt(e.DeltaManipulation.Rotation, e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
			matrix.ScaleAt(e.DeltaManipulation.Scale.X, e.DeltaManipulation.Scale.Y, e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
			foreach (Stroke stroke in inkCanvas.Strokes)
			{
				stroke.Transform(matrix, false);
				stroke.DrawingAttributes.Width *= e.DeltaManipulation.Scale.X;
				stroke.DrawingAttributes.Height *= e.DeltaManipulation.Scale.Y;
			}
		}

		private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
		{
			Settings.Default.IsDirty = true;
			foreach (Stroke stroke in e.Added)
			{
				stroke.StylusPointsChanged += stroke_StylusPointsChanged;
			}
			foreach (Stroke stroke in e.Removed)
			{
				stroke.StylusPointsChanged -= stroke_StylusPointsChanged;
			}
		}

		private void stroke_StylusPointsChanged(object sender, System.EventArgs e)
		{
			Settings.Default.IsDirty = true;
		}

		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			if (!string.IsNullOrEmpty(Settings.Default.Filename))
			{
				OpenFromStream(new MemoryStream(Convert.FromBase64String(Settings.Default.Session)));
			}
			else
			{
				Settings.Default.IsDirty = false; // if the settings got corrupted somehow, this will prevent a warning message
				FileNew();
			}
			string[] args = Environment.GetCommandLineArgs();
			if (args.Length > 1)
			{
				try
				{
					if (Settings.Default.IsDirty && MessageBox.Show("Do you want to save your work?", "", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
					{
						FileSave();
					}
					Settings.Default.Filename = args[1];
					Settings.Default.IsNew = false;
					Settings.Default.IsDirty = false;
					using (FileStream stream = new FileStream(Settings.Default.Filename, FileMode.Open))
					{
						OpenFromStream(stream);
					}
				}
				catch
				{
					MessageBox.Show("Could not open file.");
					FileNew();
				}
			}
		}

		private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
			MemoryStream stream = new MemoryStream();
			SaveToStream(stream);
			Settings.Default.Session = Convert.ToBase64String(stream.ToArray());
			Settings.Default.Save();
		}

		private void FileNew()
		{
			if (Settings.Default.IsDirty && MessageBox.Show("Do you want to save your work?", "", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
			{
				FileSave();
			}
			inkCanvas.Strokes.Clear();
			Settings.Default.Filename = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "default.wp");
			Settings.Default.IsNew = true;
			Settings.Default.IsDirty = false;
		}

		private void FileOpen()
		{
			if (Settings.Default.IsDirty && MessageBox.Show("Do you want to save your work?", "", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
			{
				FileSave();
			}
			try
			{
				OpenFileDialog dlg = new OpenFileDialog();
				dlg.Filter = "Whitepad files (*.wp)|*.wp|All files (*.*)|*.*";
				dlg.DefaultExt = "wp";
				if (dlg.ShowDialog() != true)
				{
					return;
				}
				Settings.Default.Filename = dlg.FileName;
				Settings.Default.IsNew = false;
				Settings.Default.IsDirty = false;
				using (FileStream stream = new FileStream(Settings.Default.Filename, FileMode.Open))
				{
					OpenFromStream(stream);
				}
			}
			catch
			{
				MessageBox.Show("Could not open file.");
				FileNew();
			}
		}

		private void FileSave()
		{
			try
			{
				if (Settings.Default.IsNew)
				{
					FileSaveAs();
					return;
				}
				Settings.Default.IsDirty = false;
				using (FileStream stream = new FileStream(Properties.Settings.Default.Filename, FileMode.Create))
				{
					SaveToStream(stream);
				}
			}
			catch
			{
				MessageBox.Show("Could not save file.");
			}
		}

		private void FileSaveAs()
		{
			SaveFileDialog dlg = new SaveFileDialog();
			dlg.Filter = "Whitepad files (*.wp)|*.wp|All files (*.*)|*.*";
			dlg.DefaultExt = "wp";
			if (dlg.ShowDialog() != true)
			{
				return;
			}
			Settings.Default.Filename = dlg.FileName;
			Settings.Default.IsNew = false;
			FileSave();
		}

		private void SaveToStream(Stream stream)
		{
			using (BinaryWriter writer = new BinaryWriter(stream))
			{
				writer.Write(inkCanvas.Strokes.Count);
				foreach (Stroke stroke in inkCanvas.Strokes)
				{
					writer.Write(stroke.StylusPoints.Count);
					writer.Write(stroke.DrawingAttributes.Color.A);
					writer.Write(stroke.DrawingAttributes.Color.R);
					writer.Write(stroke.DrawingAttributes.Color.G);
					writer.Write(stroke.DrawingAttributes.Color.B);
					writer.Write(stroke.DrawingAttributes.Width);
					writer.Write(stroke.DrawingAttributes.Height);
					foreach (StylusPoint point in stroke.StylusPoints)
					{
						writer.Write(point.X);
						writer.Write(point.Y);
						writer.Write(point.PressureFactor);
					}
				}
			}
		}

		private void OpenFromStream(Stream stream)
		{
			bool isDirty = Settings.Default.IsDirty;
			using (BinaryReader reader = new BinaryReader(stream))
			{
				inkCanvas.Strokes.Clear();
				int istrokes = reader.ReadInt32();
				for (int istroke = 0; istroke < istrokes; istroke++)
				{
					StylusPointCollection points = new StylusPointCollection();
					int ipoints = reader.ReadInt32();
					byte a = reader.ReadByte();
					byte r = reader.ReadByte();
					byte g = reader.ReadByte();
					byte b = reader.ReadByte();
					Color color = Color.FromArgb(a, r, g, b);
					double width = reader.ReadDouble();
					double height = reader.ReadDouble();
					for (int ipoint = 0; ipoint < ipoints; ipoint++)
					{
						StylusPoint point = new StylusPoint();
						point.X = reader.ReadDouble();
						point.Y = reader.ReadDouble();
						point.PressureFactor = reader.ReadSingle();
						points.Add(point);
					}
					Stroke stroke = new Stroke(points);
					stroke.DrawingAttributes.Color = color;
					stroke.DrawingAttributes.Width = width;
					stroke.DrawingAttributes.Height = height;
					inkCanvas.Strokes.Add(stroke);
				}
			}
			Settings.Default.IsDirty = isDirty;
		}
	}
}
